﻿using Common.FrontEnd;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Text;
using Util;
using Util.Props;

namespace FrontEnd.Util
{
    /// <summary>
    /// Produces Mel-cepstrum data from an InputStream. To set the inputstream with cepstral data, use the {@link
    /// #setInputStream(InputStream,Boolean) setInputStream} method, and then call {@link #getData} to obtain the Data
    /// objects that have cepstra data in it.
    /// </summary>
    public class StreamCepstrumSource: BaseDataProcessor
    {
        /** The property specifying whether the input is in binary. */
        [S4Boolean(defaultValue = true)]
        public static String PROP_BINARY = "binary";

        /** The property  name for frame size in milliseconds. */
        [S4Double(defaultValue = 25.625)]
        public static String PROP_FRAME_SIZE_MS = "frameSizeInMs";

        /** The property  name for frame shift in milliseconds, which has a default value of 10F. */
        [@S4Double(defaultValue = 10.0)]
        public static String PROP_FRAME_SHIFT_MS = "frameShiftInMs";

        /** The property  specifying the length of the cepstrum data. */
        [S4Integer(defaultValue = 13)]
        public static String PROP_CEPSTRUM_LENGTH = "cepstrumLength";

        /** The property specifying whether the input data is big-endian. */
        [@S4Boolean(defaultValue = false)]
        public static String PROP_BIG_ENDIAN_DATA = "bigEndianData";

        /** The property that defines the sample rate */
        [S4Integer(defaultValue = 16000)]
        public static String PROP_SAMPLE_RATE = "sampleRate";

        private Boolean binary;
        private ExtendedStreamTokenizer est; // for ASCII files
        private FileStream binaryStream; // for binary files
        private int numPoints;
        private int curPoint;
        private int cepstrumLength;
        private int frameShift;
        private int frameSize;
        private int sampleRate;
        private long firstSampleNumber;
        private Boolean _bigEndian;

        public StreamCepstrumSource( int cepstrumLength, Boolean binary, float frameShiftMs, float frameSizeMs, int sampleRate ) 
        {
            this.cepstrumLength = cepstrumLength;
            this.binary = binary;
            this.sampleRate = sampleRate;
            this.frameShift = DataUtil.getSamplesPerWindow(sampleRate, frameShiftMs);
            this.frameSize = DataUtil.getSamplesPerShift(sampleRate, frameSizeMs);
        }

        public StreamCepstrumSource( ) 
        {
        }

        /*
       /// (non-Javadoc)
        *
       /// @see edu.cmu.sphinx.util.props.Configurable#newProperties(edu.cmu.sphinx.util.props.PropertySheet)
        */
        override public void newProperties(PropertySheet ps)
        {
            base.newProperties(ps);
            cepstrumLength = ps.getInt(PROP_CEPSTRUM_LENGTH);
            binary = ps.getBoolean(PROP_BINARY);
            _bigEndian = ps.getBoolean(PROP_BIG_ENDIAN_DATA);
            float frameShiftMs = ps.getFloat(PROP_FRAME_SHIFT_MS);
            float frameSizeMs = ps.getFloat(PROP_FRAME_SIZE_MS);
            sampleRate = ps.getInt(PROP_SAMPLE_RATE);
            frameShift = DataUtil.getSamplesPerWindow(sampleRate, frameShiftMs);
            frameSize = DataUtil.getSamplesPerShift(sampleRate, frameSizeMs);        
        }


        /** Constructs a StreamCepstrumSource that reads MelCepstrum data from the given path. */
        
        public void initialize() 
        {
            base.initialize();
            curPoint = -1;
            firstSampleNumber = 0;
            _bigEndian = false;
        }


        /**
        /// Sets the InputStream to read cepstral data from.
         *
        /// @param is        the InputStream to read cepstral data from
        /// @param bigEndian true if the InputStream data is in big-endian, false otherwise
        /// @throws IOException if an I/O error occurs
         */
        public void setInputStream(FileStream _is, Boolean bigEndian)
        {
            this._bigEndian = bigEndian;
            if (binary) 
            {
                binaryStream = _is;
                if (_bigEndian) 
                {
                    numPoints = readInt(binaryStream);
                    System.Diagnostics.Trace.WriteLine("BigEndian");
                } 
                else {
                    numPoints = Utilities.readLittleEndianInt(binaryStream);
                    Trace.WriteLine("LittleEndian");
                }
                Trace.WriteLine("Frames: " + numPoints / cepstrumLength);
            } else {
                est = new ExtendedStreamTokenizer(_is, false);
                numPoints = est.getInt("num_frames");
                est.expectString("frames");
            }
            curPoint = -1;
            firstSampleNumber = 0;
        }


        public int readInt(FileStream dis)
        {
            int val = dis.ReadByte() << 24 | dis.ReadByte() << 16 | dis.ReadByte() << 8 | dis.ReadByte();
            return val;
        }

        public float readFloat(FileStream dis)
        {
            int val = readInt(dis);
            return Convert.ToSingle(val);
        }

        /**
        /// Returns the next Data object, which is the mel cepstrum of the input frame. However, it can also be other Data
        /// objects like DataStartSignal.
         *
        /// @return the next available Data object, returns null if no Data object is available
        /// @throws DataProcessingException if a data processing error occurs
         */
        public IData getData()
        {

            IData data;

            if (curPoint == -1) {
                data = new DataStartSignal(sampleRate);
                curPoint++;
            } else if (curPoint == numPoints) {
                if (numPoints > 0) {
                    firstSampleNumber =
                            (firstSampleNumber - frameShift + frameSize - 1);
                }
                // send a DataEndSignal
                int numberFrames = curPoint / cepstrumLength;
                int totalSamples = (numberFrames - 1)* frameShift + frameSize;
                long duration = (long)
                        (((double) totalSamples / (double) sampleRate)* 1000.0);

                data = new DataEndSignal(duration);

                try {
                    if (binary) {
                        binaryStream.Close();
                    } else {
                        est.close();
                    }
                    curPoint++;
                } catch (IOException ioe) {
                    throw new Exception("IOException closing cepstrum stream", ioe);
                }
            } else if (curPoint > numPoints) {
                data = null;
            } else {
                double[] vectorData = new double[cepstrumLength];

                for (int i = 0; i < cepstrumLength; i++) {
                    try {
                        if (binary) {
                            if (_bigEndian) 
                            {
                                vectorData[i] = readFloat(binaryStream);
                            } else {
                                vectorData[i] = Utilities.readLittleEndianFloat(binaryStream);
                            }
                        } else {
                            vectorData[i] = est.getFloat("cepstrum data");
                        }
                        curPoint++;
                    } catch (IOException ioe) {
                        throw new Exception("IOException reading from cepstrum stream", ioe);
                    }
                }

                // System.out.println("Read: " + curPoint);
                data = new DoubleData
                        (vectorData, sampleRate, firstSampleNumber);
                firstSampleNumber += frameShift;
                // System.out.println(data);
            }
            return data;
        }
    }
}
